package com.algorithms.brownian;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import com.algorithms.brownian.pq.MinPQ;
import com.algorithms.libs.stdlib.draw.StdDraw;
import com.algorithms.libs.stdlib.io.StdIn;

public class CollisionSystem {

	private MinPQ<CollisionEvent> pq; // the priority queue
	private double t = 0.0; // simulation clock time
	private double hz = 0.5; // number of redraw events per clock tick
	private Particle[] particles; // the array of particles

	// create a new collision system with the given set of particles
	public CollisionSystem(Particle[] particles) {
		this.particles = particles.clone(); // defensive copy
	}

	private void predict(Particle a, double limit) {
		if (a == null) {
			return;
		}

		for (int i = 0; i < particles.length; i++) {

			double deltaTime = a.timeToHit(particles[i]);
			if (deltaTime < 0) {
				System.out.println("delta time less than 0:" + deltaTime);
			}
			if (t + deltaTime <= limit) {
				CollisionEvent event = new CollisionEvent(t + deltaTime, a,
						particles[i]);
//				System.out.println("Event avx: " + event.a.vx);
//				System.out.println("Event avy: " + event.a.vy);
//				System.out.println("Event bvx: " + event.b.vx);
//				System.out.println("Event bvy: " + event.b.vy);
				pq.insert(event);
			}
		}

		double dtHWall = a.timeToHitHorizontalWall();
		double dtVWall = a.timeToHitVerticalWall();
		if (t + dtHWall <= limit) {
			pq.insert(new CollisionEvent(t + dtHWall, a, null));
		}
		if (t + dtVWall <= limit) {
			pq.insert(new CollisionEvent(t + dtVWall, null, a));
		}

	}

	/********************************************************************************
	 * Event based simulation for limit seconds
	 ********************************************************************************/
	public void simulate(double limit) {

		// initialize PQ with collision events and redraw event
		pq = new MinPQ<CollisionEvent>();
		for (int i = 0; i < particles.length; i++) {
			predict(particles[i], limit);
		}
		pq.insert(new CollisionEvent(0, null, null)); // redraw event

		// the main event-driven simulation loop
		while (!pq.isEmpty()) {

			// get impending event, discard if invalidated
			CollisionEvent e = pq.delMin();
			if (!e.isValid())
				continue;
			Particle a = e.a;
			Particle b = e.b;

			// physical collision, so update positions, and then simulation
			// clock
			for (int i = 0; i < particles.length; i++)
				particles[i].move(e.time - t);
			t = e.time;

			// process event
			if (a != null && b != null) {
				a.bounceOffWithoutAngles(b); // particle-particle collision
			} else if (a != null && b == null) {
				a.bounceOffVerticalWall(); // particle-wall collision
			} else if (a == null && b != null) {
				b.bounceOffHorizontalWall(); // particle-wall collision
			} else if (a == null && b == null) {
				redraw(limit); // redraw event
			}

			// update the priority queue with new collisions involving a or b
			predict(a, limit);
			predict(b, limit);
		}
	}

	// redraw all particles
	private void redraw(double limit) {
		StdDraw.clear();
		for (int i = 0; i < particles.length; i++) {
			particles[i].draw();
		}
		StdDraw.show(20);
		if (t < limit) {
			pq.insert(new CollisionEvent(t + 1.0 / hz, null, null));
		}
	}

	public static void main(String[] args) {

		StdDraw.setCanvasSize(800, 800);

		// remove the border
		StdDraw.setXscale(1.0 / 22.0, 21.0 / 22.0);
		StdDraw.setYscale(1.0 / 22.0, 21.0 / 22.0);

		// turn on animation mode
		StdDraw.show(0);

		// the array of particles
		Particle[] particles;
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(args[0]));
			if (args.length == 1) {
				int N = Integer.parseInt(reader.readLine());
				N = 70;
				particles = new Particle[N];
				for (int i = 0; i < N; i++)
					particles[i] = new Particle();
			}

			// or read from standard input
			else {
				int N = StdIn.readInt();
				particles = new Particle[N];
				for (int i = 0; i < N; i++) {
					double rx = StdIn.readDouble();
					double ry = StdIn.readDouble();
					double vx = StdIn.readDouble();
					double vy = StdIn.readDouble();
					double radius = StdIn.readDouble();
					double mass = StdIn.readDouble();
					int r = StdIn.readInt();
					int g = StdIn.readInt();
					int b = StdIn.readInt();
					Color color = new Color(r, g, b);
					particles[i] = new Particle(rx, ry, vx, vy, radius, mass,
							color);
				}
			}
			// create N random particles

			// create collision system and simulate
			CollisionSystem system = new CollisionSystem(particles);
			system.simulate(10000);
		} catch (FileNotFoundException e) {
			System.out.println("Exception in reading a file:" + e.getMessage());
		} catch (IOException ex) {
			System.out
					.println("Exception in reading a file:" + ex.getMessage());
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					System.out.println("Error in closing:" + e.getMessage());
				}
			}
		}
	}
}
